/*
 * xTree 1.0 - Plugin for jQuery
 * 
 * Copyright (c) 2008 Oleg Slobodskoi (ajaxsoft.de)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * Depends:
 *   jquery.js
 * Optional
 * 	 ui.core.js
 *	 ui.selectable.js
 */
;(function($) {

	$.fn.extend({
		xTree: function(method, options, callback) {
			var args = Array.prototype.splice.call(arguments, 1,arguments.length);

			if (typeof method == 'object')
			{
				options = method;
				method = null;
			};

			return this.each(function() {
				var xTree = $.data(this, 'xTree') || $.data(this, 'xTree', new $.xTree(this, options));
				method ?  xTree[method].apply(xTree, args) : xTree.init();
			});
			
		}
	});


	$.xTree = function(elem, options)
	{
		
		//defaults
		var d = {
			elements: [{
				name: null,
				icon: null,
				id: null,
				iconAttr: null,
				nameAttr: null,
				onIconClick: null,
				onNameClick: null,
				onOpen: null,
				onClose: null,
				folder: true,
				root: false,
				elements: []
			
			}],
			animate: true,
			speed: 150,
			folderIcon: 'img/folder.gif',
			fileIcon: 'img/folder.gif',
			multipleSelect: true,
			selectableOptions: {
				appendTo: elem,
				autoRefresh: true,
				filter: '.name',
				tolerance: 'touch',
				selected: function(e, obj){
					select(obj.selected);
				},
				unselected: function(e, obj){
					unselect(obj.unselected);
				}
				
			}

		};
		
		
		$.extend(d,options);	
		

		var self = this,
			$elem = $(elem),
			selected = [],
			root
		;
		

		/************************************************************************************************************************/
		//Public methods

		this.defaults = function (o)
		{
			$.extend(d,o);	
		};
		
		this.init = function (o)
		{
			init();
		};

		this.destroy = function()
		{	
			$.removeData(elem, 'xTree');
			$elem.empty().removeClass('xTree');
		};

		this.remove = function(id)
		{
			if (id)
			{	
				if (typeof id == 'object')
					$.each(id, function(i,val){
						remove($('#'+val));
					});
				else
					remove($('#'+id));
				
			};
		};
		
		this.select = function(id)
		{
			id && !/function/i.test(id) ? $('#'+id+' .name').mousedown() : createCallback(id,[self, selected]);
		};
		
		this.close = function(id)
		{
			close($('#'+id));
		};

		this.closeBelow = function(id)
		{
			$('#'+id).find('li').each(function(){
				close($(this));
			});
			self.close(id);
		};

		this.closeAll = function()
		{
			$elem.find('li').each(function(){
				close($(this));
			});
		};

		this.open = function(id)
		{
		
			if (id && !/function/i.test(id))
			{
				typeof id == 'string' ?
				openTill(id) :
				$.each(id,function(i,id){
					openTill(id);
				}); 
				

			}
			else
			{
				//thats callback
				var f = id;
				id = [];
				$elem.find('li').each(function(i,val){
					$(this).parent().css('display')!='none' && id.push(val.id);
				});
				createCallback(f,[self,id]);
			};


			function openTill (id)
			{
				var li = $('#'+id),
					elems = [li]
				;
	
				while (li.length)
				{
					li = li.parents('li:first');
					elems.push(li);
				};
				elems.pop();
	
				for (var i=elems.length-1;i>=0;i--)
					open(elems[i]);			
			
			};
		};

		this.update = function(o)
		{
			if (o.parent)
			{
				var $parent = $('#'+o.parent.id);
				if ($parent && $parent.length && o.elements)
				{
					var $ul = $parent.children('ul');
					!$ul.length ? $ul = $('<ul/>').appendTo($parent) : $ul.empty();
					fixAfterAdd($parent);
					add(o.elements, $ul);
				};
			
				o.parent.name && $parent.children('.name').text(o.parent.name);
				o.parent.id && $parent.attr('id', o.parent.id);
			};
			
		};
		
		this.add = function(o)
		{
			var $parent = o.parent ? $('#'+o.parent) : $elem;
			if ($parent.length)
			{
				var $ul = $parent.children('ul');
				if (!$ul.length)
					$ul = $('<ul/>').appendTo($parent);
				fixAfterAdd($parent);
				add(o.elements, $ul);
			};	
		};
		
		this.empty = function(parentId)
		{
			var $parent = $('#'+parentId);
			$parent.children('ul').empty();
			fixAfterAdd($parent);
		};
		
		this.enable = function(id, all)
		{	
			$('#'+id)[all ? 'find' : 'children']('.icon, .name').removeClass('disabled');
		};

		this.disable = function(id, all)
		{
//			console.log(all.toString());
			$('#'+id)[all ? 'find' : 'children']('.icon, .name').addClass('disabled');
		};
		
		
		


		
			
		/************************************************************************************************************************/
		//Private methods

		function init ()
		{	
			$elem.empty().addClass('xTree');
			root = $('<ul class="first"/>').appendTo($elem);
			d.multipleSelect && $.fn.selectable && $elem.selectable(d.selectableOptions);
			add(d.elements,root);	
		};	

		function add (elements, parent)
		{
			var li,ul,branch,toggler, icon, attr = {};
			
			$.each(elements, function(i,val){
				$.extend(attr, val.attr, {id:val.id});
				li = $('<li/>').attr(attr).
				appendTo(parent);
	
				if (!val.root)
				{
					toggler = $('<span/>').appendTo(li);
					(val.elements && val.elements.length ) || val.folder ? toggler.addClass('plus') : toggler.addClass('branch');
				
					i>= elements.length-1 && toggler.attr('class', toggler.attr('class')+'-last');
					toggler.mousedown(function(){
						if ( $(this).attr('class').indexOf('branch') == -1)
							toggle($(this),val.onOpen||null);
					});
					
				};

				icon = (val.elements && val.elements.length) ? d.folderIcon : d.fileIcon;
				if (val.icon) icon = val.icon;
				
				$('<span/>').attr(val.iconAttr||{}).addClass('icon').css("background-image","url("+icon+")").
				mousedown(function(){
					select($(this).next().get(0));
					clickHandler(this, val.onIconClick || null);

				}).
				addClass(val.disabled ? 'disabled' : '').
				appendTo(li);
				
				$('<span>'+val.name+'</span>').attr(val.nameAttr||{}).addClass('name ').addClass(val.disabled ? 'disabled' : '').
				mousedown(function(){
					unselect();
					select(this);
					clickHandler(this, val.onNameClick || null);
				}).
				appendTo(li);	

				if ( val.elements && val.elements.length )
				{
					ul = $('<ul/>').appendTo(li);
					i< elements.length-1 && ul.addClass('line');
					add(val.elements,ul);
				};
	
				
			});		
			
			
			return self;		
		};
		
		function clickHandler (elem, event)
		{
			createCallback(event, [$(elem).parent('li').attr('id')]);
		};
		
		function remove (elem) 
		{	
			var parent = elem.parent().parent();
			elem.remove();
			fixAfterRemove(parent);
		};

		//fixing the togglers, branches and lines
		function fixAfterRemove (li)
		{	
			var toggler, prevToggler;
			var prevLI = li.children('ul').children('li:last');
			
			//if previous element is the last one
			if (prevLI.length)
			{
				prevLI.children('ul').removeClass('line');
				prevToggler = prevLI.children('span:first');
				if (prevToggler.attr('class').indexOf('last') == -1)
					prevToggler.attr('class', prevToggler.attr('class')+'-last');
			};
			
			//if the element empty now
			if ( !li.find('li').length)
				//if the element is the last one
				li.get(0) == li.parent('ul').children('li:last').get(0) ? li.children('span:first').attr('class','branch-last') : li.children('span:first').attr('class','branch');
				

			
			return self;
		};

		//fixing the togglers, branches and lines
		function fixAfterAdd (li)
		{	
			var toggler, prevToggler;
			var prevLI = li.children('ul').children('li:last');
			
			//if previous element is the last one
			if (prevLI.length)
			{
				prevLI.children('ul').addClass('line');
				prevToggler = prevLI.children('span:first');
				prevToggler.attr('class', prevToggler.attr('class').replace(/-last/,''));
			};
			
			toggler = li.children('span:first');
			if (toggler.length)
			{
				//if the element was empty before inserting
				li.children('ul:visible').length ? toggler.attr('class', 'minus') : toggler.attr('class', 'plus');
				
				//if the element is the last one
				if (li.get(0) == li.parent('ul').children('li:last').get(0))
					toggler.attr('class', toggler.attr('class')+'-last');
				else
					li.children('ul').addClass('line');
			};
			
			
			
			return self;
		};

		function toggle (elem, callback) 
		{
			elem.attr('class').indexOf('plus') != -1 ? open(elem.parent(), callback) : close(elem.parent(),callback);
			return self;
		};
		
				

		function unselect (elem)
		{
			var id=$(elem).parent().attr('id');

			if (elem)		
			{
				var i = $.inArray(id, selected);

				if (i && i!=-1)
				{
					selected.splice(i,1);
					$(elem).removeClass('selected');
				};
			} 
			//unselect all elements
			else
			{
				$.each(selected, function(i,val){
					$('#'+val+' .name').removeClass('selected');
				});
				selected = [];
			};

		};
		
		function select (elem)
		{
			var id = $(elem).parent().attr('id');
			if ( $.inArray(id, selected) == -1);
				selected.push(id);
			$(elem).addClass('selected');
	
			return self;
		};
				
		function open (elem, onOpen)
		{
			var toggler,
				ul,
				onOpen = onOpen || function(){}
			;		
			
			ul = elem.children('ul');
			
			if (ul.children('li').length)
			{
				d.animate ? ul.slideDown(d.speed) : ul.show();
				toggler = elem.children('span:first');
				toggler.attr('class', toggler.attr('class').replace('plus','minus'));				
			} 
			else
				createCallback(onOpen,[elem.attr('id')]);

			return self;
		};
		
		function close (elem, onClose)
		{
			var toggler = elem.children('span:first'),
				ul = elem.children('ul')
			;
			d.animate ? ul.slideUp(d.speed) : ul.hide();
			toggler.attr('class', toggler.attr('class').replace('minus','plus'));	
			return self;
		};
		
		
		function createCallback (f,params)
		{		
			typeof f == 'string' && eval('f='+f);
			$.isFunction(f) && f.apply(f,params);
		};		

	};

})(jQuery);		